package messagemanager;

import FileWorks.FileStructure;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Vector;

/** <b>Класс менеджера сообщений</b><br>
 * @author Караваев Кирилл, ID 1004
 * @version 0.1 Базовая функциональность
 */
public class JMessageManager
{
    /** Размер блоков данных для файловой структуры.
     * Для увеличения эффективности использования ресурсов следует собрать статистику
     * по среднему количеству сообщений в очереди для всех пользователей
     * и это количество записать в DEFAULT_BLOCKSIZE */
    private final int DEFAULT_BLOCKSIZE = 16;
    /** Максимальное количестов пользователей в системе */
    int UsersAmount = 0;
    /** Массив слушателей событий для каждого пользователя */
    private Vector<JMessageListener> Listeners = null;
    /** Файловая структура для сериализации очереди сообщений сообщений */
    private FileStructure<JMessage> Storage = null;
    
//=============================================================================
    /** <b>Инициация события oneNewMessage пользовательского слушателя</b><br>
     * Следует вызывать при поступлении нового сообщения для пользователя, если
     * этот пользователь активен
     * @param UserID идентификатор пользователя
     * @param Msg передаваемое сообщение
     */
    private void fireOneNewMessage(int UserID, JMessage Msg)
    {
	Listeners.get(UserID).oneNewMessage(Msg);
    }
    
    /** <b>Инициация события unreadMessagesDetected пользательского слушателя</b><br>
     * Следует вызывать при входе пользователя в систему, если для него есть
     * непрочитанные сообщения
     * @param UserID идентификатор пользователя
     */
    private void fireUnreadMessagesDetected(int UserID)
    {
	LinkedList<JMessage> MS;
	try
	{
	    MS = getMessages(UserID);
	} catch (Exception e)
	{
	    System.out.println("JMessageManager: getMessages error detected");
	    return;
	}
	Listeners.get(UserID).unreadMessagesDetected(MS);
    }
//=============================================================================    
    /** Конструктор<br>
     * Система будет расчитана на <code>UsersAmount</code> пользователей
     * @param UsersAmount максимально возможное количество пользователей в системе
     */
    public JMessageManager(int UsersAmount)
    {
	this.UsersAmount = UsersAmount;
	Listeners = new Vector<JMessageListener>(UsersAmount);
	Listeners.setSize(UsersAmount);
    }
    
    /** <b>Использовать для хранения сообщений файловую структуру</b><br>
     * При этом, если указанные файлы существуют, будет произведена попытка
     * загрузить файловую структуру из этих файлов, иначе файлы будут созданы
     * @param MFName имя главного файла описания структуры
     * @param DFName имя файла с данными
     * @throws IOException если операции с файлами не могут быть выполнены
     */
    synchronized public void useStorage(String MFName, String DFName)
	    throws IOException
    {
	//Если хранилище уже загружено - выходим
	if (Storage != null) return;
	File MF = new File(MFName), DF = new File(DFName);
	Storage = new FileStructure<JMessage>();
	try
	{
	    synchronized(Storage)
	    {
		if (MF.exists() && DF.exists()) Storage.loadStorage(MFName);
		else Storage.createDataStorage(MFName, DFName, UsersAmount, DEFAULT_BLOCKSIZE);
	    }
	} catch (IOException e)
	{
	    Storage = null;
	    throw e;
	}
    }
    
    /** <b>Отмена использования файлового хранилища</b> */
    synchronized public void closeStorage()
	    throws IOException
    {
	if (Storage != null)
	{
	    synchronized(Storage)
	    {
		Storage.closeStorage();
	    }
	    Storage = null;
	}
    }
    
    /** <b>Добавить пользовательского слушателя</b><br>
     * При этом, если есть непрочитанные сообщения, инициируется событие
     * unreadMessagesDetected и производится попытка отправки сообщений пользователю
     * @param UserID идентификатор пользователя
     * @param L экземпляр слушателя от конкретного пользователя
     * @throws IllegalArgumentException при задании неверных параметров
     */
    public void addListener(int UserID, JMessageListener L)
	    throws IllegalArgumentException
    {
	if (UserID < 0 || UserID >= UsersAmount || L == null)
	    throw new IllegalArgumentException();
	Listeners.set(UserID, L);
	if (isMessageExists(UserID)) fireUnreadMessagesDetected(UserID);
    }
    
    /** <b>Удалить пользовательского слушателя</b><br>
     * @param UserID идентификатор пользователя
     * @throws IllegalArgumentException при неверном задании ID
     */
    public void removeListener(int UserID)
	    throws IllegalArgumentException
    {
	if (UserID < 0 || UserID >= UsersAmount)
	    throw new IllegalArgumentException();
	Listeners.remove(UserID);
    }
    
    /** <b>Добавить новое сообщение для пользователя</b><br>
     * Если пользователь активен, сообщение сразу отсылается ему,
     * метод возвращает <code>true</code>.<br>
     * В противном случае, если используется файловое хранилище, сообщение
     * добавляется в очередь и сохраняется в файле.<br>
     * Если же файловое хранилище не используется, метод возвращает <code>false</code>.
     * @param UserID идентификатор пользователя
     * @param Msg новое сообщение
     * @throws IllegalArgumentException если заданы некорректные параметры
     */
    synchronized public boolean addMessage(int UserID, JMessage Msg)
	    throws IllegalArgumentException
    {
	if (Msg == null || UserID < 0 || UserID >= UsersAmount)
	    throw new IllegalArgumentException();
	if (Listeners.get(UserID) != null)
	{
	    fireOneNewMessage(UserID, Msg);
	    return true;
	}
	if (Storage == null) return false;
	boolean ret;
	synchronized(Storage)
	{
	    ret = Storage.addFor(UserID, Msg);
	}
	return ret;
    }
    
    /** <b>Проверка существования для пользователя очереди сообщений</b><br>
     * Если файловая структура не используется, метод возвращает <code>false</code>
     * @param UserID идентификатор пользователя
     * @throws IllegalArgumentException при неверном задани параметра
     */
    synchronized public boolean isMessageExists(int UserID)
	    throws IllegalArgumentException
    {
	if (UserID < 0 || UserID >= UsersAmount) throw new IllegalArgumentException();
	if (Storage == null) return false;
	boolean ret = false;
	synchronized(Storage)
	{
	    ret = Storage.existsDataFor(UserID);
	}
	return ret;
    }
    
    /** <b>Получить очередь сообщений для пользователя</b><br>
     * Если файловая структура не используется, метод возвращает <code>null</code>
     * @param UserID идентификатор пользователя
     * @throws IOException невозможна работа с файловой структурой
     * @throws IllegalArgumentException неверно задан ID
     */
    synchronized public LinkedList<JMessage> getMessages(int UserID)
	    throws IOException, InstantiationException, IllegalAccessException,
	    IllegalArgumentException
    {
	if (UserID < 0 || UserID >= UsersAmount) throw new IllegalArgumentException();
	if (Storage == null) return null;
	LinkedList<JMessage> RetList = null;
	synchronized(Storage)
	{
	    RetList = Storage.removeAllFor(UserID, new JMessage());
	}
	return RetList;
    }
}
